package growth.net.register;

import graph.plotter.Jung;
import growth.net.peer.MessageTypes;
import growth.net.peer.Node;
import growth.net.peer.RequestProcessor;
import growth.net.peer.SendingService;
import growth.net.utils.Utils;
import java.io.IOException;
import java.net.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

public class GraphGenerator extends Thread {

    private final Integer MUTEX = 0;
    private Integer insertionKey = 0;
    private String SCRIPT = "/opt/growthnet/node_script.sh";
    private int REG_PORT = 3000;
    private InetAddress REG_IP = null;
    private int PACKETSIZE = 1024;
    private int NODE_COUNT = 0;
    public HashMap<Integer, Node> registerTable = null;
    public HashSet<String> nodeSet = null;
    private Jung jung = null;

    public void startRegisterListner() {
        Thread registrationListener = new Thread(this);
        registrationListener.start();
    }

//    RequestProcessor requestProcessor = new RequestProcessor();
    public static void main(String[] args) {
        GraphGenerator msgInjector = new GraphGenerator();
        System.out.print("Enter Node Count: ");
        msgInjector.NODE_COUNT = Utils.getInteger();
        msgInjector.startRegisterListner();
        msgInjector.createNodes(msgInjector.NODE_COUNT);
        msgInjector.startJungEngine();
        msgInjector.generateRandomGraph();
        while (true) {
            Utils.showMenuGraphGen();
            int option = Utils.getInteger();
            switch (option) {
                case 0:
                    msgInjector.selectRandomAndSendLeave();
                    break;
                case 1:
                    msgInjector.selectRandomAndSendSpawn();
                    break;
                default:
            }
        }

    }

    private void selectRandomAndSendSpawn() {
        Random random = new Random();
        RequestProcessor requestProcess = new RequestProcessor();
        System.out.print("Probability for node selection (in %): ");
        int probability = Utils.getInteger();
        while (probability > 100 || probability < 0) {
            System.out.println("Invalid value, choose again !!");
            System.out.print("Probability for node selection (in %): ");
            probability = Utils.getInteger();
        }
        int howMany = ((NODE_COUNT * probability)) / 100;
        int selectThis = -1;
        Set<Integer> keys = null;
        Iterator iter = null;
        Node node = null;
        System.out.println("Spawning " + howMany + " nodes more...");
        Set<Integer> spawnIndex = new HashSet<Integer>();
        for (int i = 0; i < howMany; i++) {
            keys = registerTable.keySet();
            iter = keys.iterator();
            selectThis = random.nextInt(registerTable.size());
            while (spawnIndex.add((Integer) selectThis) == false) {
                selectThis = random.nextInt(registerTable.size());
            }
            for (int j = 0; j < selectThis; j++) {
                iter.next();
            }
            int selectedIndex = (int) iter.next();
            node = registerTable.get(selectedIndex);
            requestProcess.sendMessageToSpawn(node);
        }
    }

    private void selectRandomAndSendLeave() {
        Random random = new Random();
        RequestProcessor requestProcess = new RequestProcessor();
        System.out.print("Probability for node selection (in %): ");
        int probability = Utils.getInteger();
        while (probability > 100 || probability < 0) {
            System.out.println("Invalid value, choose again !!");
            System.out.print("Probability for node selection (in %): ");
            probability = Utils.getInteger();
        }
        int howMany = ((NODE_COUNT * probability)) / 100;
        int selectThis = -1;
        Set<Integer> keys = null;
        Iterator iter = null;
        Node node = null;
        System.out.println("Leaving nodes: " + howMany + " out of " + NODE_COUNT);
        Set<Integer> removedIndex = new HashSet<Integer>();
        for (int i = 0; i < howMany; i++) {
            keys = registerTable.keySet();
            iter = keys.iterator();
            selectThis = random.nextInt(registerTable.size());
            while (removedIndex.add((Integer) selectThis) == false) {
                selectThis = random.nextInt(registerTable.size());
            }
            for (int j = 0; j < selectThis; j++) {
                iter.next();
            }
            int selectedIndex = (int) iter.next();
            node = registerTable.get(selectedIndex);
            requestProcess.sendMessageToLeave(node);
        }
    }

    private void generateRandomGraph() {
        try {
            Random random = new Random();
            RequestProcessor requestProcess = new RequestProcessor();
            Node fromNode = null;
            Node toNode = null;
            nodeSet = new HashSet<String>();
            synchronized (this.MUTEX) {
                this.MUTEX.wait();
                Set<Integer> keys = registerTable.keySet();
                Iterator iter = keys.iterator();
                while (iter.hasNext()) {
                    jung.addVertexToGraph(registerTable.get((Integer) iter.next()).name);
                }
                for (; nodeSet.size() != NODE_COUNT;) {
                    int index1 = random.nextInt(NODE_COUNT);
                    int index2 = random.nextInt(NODE_COUNT);
                    if (index1 == index2) {
                        continue;
                    }
                    System.out.println(index1 + " " + index2);
                    fromNode = registerTable.get(index1);
                    toNode = registerTable.get(index2);
                    nodeSet.add(fromNode.name);
                    requestProcess.sendAddMeRequest(fromNode.name, fromNode.listPort,
                            fromNode.sendPort, fromNode.ip, toNode.listPort, toNode.ip);
                    requestProcess.sendAddMeRequest(toNode.name, toNode.listPort,
                            toNode.sendPort, toNode.ip, fromNode.listPort, fromNode.ip);
                    jung.addEdgeToGraph(registerTable.get(index1).name, registerTable.get(index2).name);
                }
                jung.showGraph(jung.graph);
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }

    private void createNodes(int nodeCount) {
        System.out.println("Creating " + NODE_COUNT + " Nodes...");
        try {
            registerTable = new HashMap<Integer, Node>();
            String[] params = {SCRIPT, String.valueOf(REG_PORT)};
            for (int count = 0; count < nodeCount; count++) {
                Runtime.getRuntime().exec(params);
            }
        } catch (IOException ex) {
            Logger.getLogger(GraphGenerator.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    int nodeCount = 0;

    @Override
    public void run() {
        // Construct the socket
        DatagramSocket socket = null;
        DatagramPacket packet = null;
        String receivedMsg = null;
//        System.out.println("Registration Service started...");
        try {
            socket = new DatagramSocket(REG_PORT);
            for (;;) {
                packet = new DatagramPacket(new byte[PACKETSIZE], PACKETSIZE);
                socket.receive(packet);
                receivedMsg = new String(packet.getData());
                String[] msgParams = Utils.messageParser(receivedMsg);
                int msgType = Integer.valueOf(msgParams[0].trim().toString());

                switch (msgType) {
                    case MessageTypes.REGISTERME:
                        System.out.println("Registeratin request received ");
                        registerNode(receivedMsg);
                        nodeCount++;
                        synchronized (this.MUTEX) {
                            if (this.NODE_COUNT == nodeCount) {
                                //When all the nodes have requested to registerToGrowthNet start the linking process
                                this.MUTEX.notify();
                            }
                        }
                        break;
                    case MessageTypes.REGISTER_SPAWN:
                        System.out.println("SPAWN request received ");
                        registerNode(receivedMsg);
                        msgParams = Utils.messageParser(receivedMsg);
                        jung.addVertexToGraph(msgParams[1].toString());
                        jung.addEdgeToGraph(msgParams[1].toString(), msgParams[5].toString());
                        nodeCount++;
                        NODE_COUNT++;
                        break;
                    case MessageTypes.DELETEVERTEX:
                        parseAndDeleteVertex(receivedMsg);
                        break;
                    case MessageTypes.ADDEEDGE:
                        parseAndAddEdge(receivedMsg);
                        break;
                    case MessageTypes.ADDVERTEX:
//                        parseAndAddEdge(receivedMsg);
                        break;
                    case MessageTypes.ANALYSIS_DEG:
                        parseAndReplyDegAnalysis(receivedMsg);
                        break;
                    case MessageTypes.ANALYSIS_DIA:
                        parseAndReplyDiaAnalysis(receivedMsg);
                        break;
                    default:
                        System.out.println("Unknown message type: " + msgType);
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void registerNode(String receivedMsg) {
        try {
            String[] msgParams = Utils.messageParser(receivedMsg);
            Node node = new Node();
            node.name = msgParams[1];
            node.listPort = Integer.parseInt(msgParams[2].trim());
            node.sendPort = Integer.parseInt(msgParams[3].trim());
            String[] splitAdd = msgParams[4].trim().split("/");
            node.ip = InetAddress.getByName(splitAdd[1]);
            registerTable.put(insertionKey++, node);
        } catch (UnknownHostException ex) {
            ex.printStackTrace();
        }
    }

    private void startJungEngine() {
        jung = new Jung();
    }

    private void parseAndDeleteVertex(String receivedMsg) {
        String[] msgParams = Utils.messageParser(receivedMsg);
        Set<Integer> keySet = registerTable.keySet();
        Iterator iter = keySet.iterator();
        Integer key = null;
        while (iter.hasNext()) {
            key = (Integer) iter.next();
            if (registerTable.get(key).name.equals(msgParams[1].toString())) {
                jung.removeVertexFromGraph(registerTable.get(key).name);
                registerTable.remove(key);
                NODE_COUNT--;
                nodeCount--;
                if (registerTable.isEmpty()) {
                    System.exit(0);
                } else {
                }
                break;
            }
        }
        System.out.println("Nodes Left: " + registerTable.size());
    }

    private void parseAndAddEdge(String receivedMsg) {
        String[] msgParams = Utils.messageParser(receivedMsg);
//        System.out.println("Adding edge between: " + msgParams[1].toString() + " and " + msgParams[2].toString());
        Set<Integer> keySet = registerTable.keySet();
        Iterator iter = keySet.iterator();
        Integer keyOfVertex1 = null;
        Integer keyOfVertex2 = null;

        while (iter.hasNext()) {
            keyOfVertex1 = (Integer) iter.next();
            if (registerTable.get(keyOfVertex1).name.equals(msgParams[1].toString())) {
                break;
            }
        }
        iter = keySet.iterator();
        while (iter.hasNext()) {
            keyOfVertex2 = (Integer) iter.next();
            if (registerTable.get(keyOfVertex2).name.equals(msgParams[2].toString())) {
                break;
            }
        }
        jung.addEdgeToGraph(registerTable.get(keyOfVertex1).name, registerTable.get(keyOfVertex2).name);
    }

    private void parseAndReplyDegAnalysis(String receivedMsg) {
        String[] msgParams = Utils.messageParser(receivedMsg);
        Set<Integer> keySet = registerTable.keySet();
        Iterator iter = keySet.iterator();
        String requesterName = msgParams[1].toString();
        int minDegree = 99999999;
        int maxDegree = 0;
        int degree = 0;
        String vertexName = null;
        String minVertex = null;
        String maxVertex = null;
        Node requesterNode = null;
        Node node = null;
        while (iter.hasNext()) {
            node = registerTable.get((Integer) iter.next());
            vertexName = node.name;

            if (vertexName.equals(requesterName)) {
                requesterNode = node;
            }
            degree = jung.getVertexDegree(vertexName);
            if (degree > maxDegree) {
                maxDegree = degree;
                maxVertex = vertexName;
            }
            if (degree < minDegree) {
                minDegree = degree;
                minVertex = vertexName;
            }
        }
//        System.out.println("Max Degree: " + maxDegree + ": " + maxVertex);
//        System.out.println("Min Degree: " + minDegree + ": " + minVertex);

        SendingService.getSendingService().send(MessageTypes.ANALYSIS_DEG + "," + minDegree + "," + minVertex + ","
                + maxDegree + "," + maxVertex, requesterNode.ip, requesterNode.listPort);
        jung.highlightMinMaxDegreeNodes(minVertex, maxVertex);

    }

    private void parseAndReplyDiaAnalysis(String receivedMsg) {
        String[] msgParams = Utils.messageParser(receivedMsg);
        Set<Integer> keySet1 = registerTable.keySet();
        Set<Integer> keySet2 = registerTable.keySet();
        Iterator iter = keySet1.iterator();

        Integer key = null;
        int diameter = 0;
        int temp = 0;
        String v1 = null;
        String v2 = null;
        String dv1 = null;
        String dv2 = null;
        String requesterName = msgParams[1].toString();
//        int minDegree = 99999999;
//        int maxDegree = 0;
//        int degree = 0;
        String vertexName = null;
//        String minVertex = null;
//        String maxVertex = null;
        Node requesterNode = null;
        Node node = null;
        while (iter.hasNext()) {
            node = registerTable.get((Integer) iter.next());
            vertexName = node.name;

            if (vertexName.equals(requesterName)) {
                requesterNode = node;
            }
        }
        Iterator iter1 = keySet1.iterator();
        Iterator iter2 = keySet2.iterator();
        while (iter1.hasNext()) {
            iter2 = keySet2.iterator();
            key = (Integer) iter1.next();            
            while (iter2.hasNext()) {
                v1 = registerTable.get(key).name;
                v2 = registerTable.get((Integer) iter2.next()).name;
                temp = jung.getDiameter(v1, v2);
                if (temp > diameter) {
                    diameter = temp;
                    dv1 = v1;
                    dv2 = v2;
                }
            }
        }
        SendingService.getSendingService().send(MessageTypes.ANALYSIS_DIA + "," + diameter, requesterNode.ip, requesterNode.listPort);
        jung.highlightDiameterNodes(dv1, dv2);
//        System.out.println("Diameter : " + diameter);
    }
}
